#/usr/bin/perl

################################################################################
# metar.pl V 1.0 8/29/11  Nick Larkin
#
# This script will return either a color-coded (based on flight rule)
# METAR or TAF for the stations that are input.  METARs older than 70 mins are
# shown in yellow.  METARs that are missing wind or ceiling data are shown
# uncolored.
#
# Options:
#	-t Output TAF
#	-b Output in bold (works better in geektool)
#
# Usage:
# 	METAR:	perl metar.pl stations
#		Example: perl metar.pl kowd kbos KPYM KaCk (case insensitive)
#
#	TAF: 	perl metar.pl -t stations
#		Example: perl metar.pl kbos KACK (case insensitive)
#		Note: Not all stations have TAFs.  Choose carefully
#
#	BOLD:	perl metar.pl -b kowd
#		METAR in bold
#
#		perl metar.pl -tb kbos
#		TAF in bold
################################################################################

use Getopt::Std;
use Term::ANSIColor qw(:constants);
use Geo::METAR;
use Switch;

my %options=();
getopts("tb", \%options);


if (!$options{t})
	{
		#Iterate throughout the stations that were input
		foreach my $station (@ARGV)
		{
			my $flightRule = "";
			my $m = new Geo::METAR;
			
			#Remove date and new lines and then create a new metar object
			my $METARoutput = &processMETAR("$station");
			$m->metar("$METARoutput");
			
			#grab the date and time from the METAR
			my $metarDate = $m->DATE;
			my $metarTime = $m->TIME;
			
			#figure out how old the METAR is
			my $METARAge = &compareTime("$metarDate","$metarTime");
			
			#Check the visibility and set the flight rule based on it
			my $visibility = &parseVisibilityMETAR("$METARoutput");
			my $visibilityFlightRule = &checkVisibility("$visibility");
			
			#Check all cloud layers and set the flight rule based on it.  The SKY var is an array
			#produced by the object that contains all cloud layers
			my @clouds = @{$m->SKY};
			my $ceilingFlightRule = &checkClouds(@clouds);
			
			#Determine what the flight rule is and output the METAR with the correct color
			&outputMETAR("$visibilityFlightRule", "$ceilingFlightRule", "$METARoutput", "$METARAge");
		}
	}
else
	{
		#Iterate though all the stations that were input
		foreach my $station (@ARGV)
		{	
			#Need to fetch the TAF first and then process it to look like a METAR so the METAR lib can process it
			#I need to extract the Zulu time to make it look like a METAR
			my $TAFoutput = &fetchTAF("$station");
			my $tempTAF = &processTAF("$TAFoutput");
			my $time = &extractTime("$tempTAF");
			
			#Remove new lines from both the taf and the temporary one that mimics a METAR
			my @TAFElements = split(/\n/, $TAFoutput);
			my @tempTAFElements = split(/\n/, $tempTAF);
			my $numElements = @TAFElements;
			
			#Process and output the TAF
			&outputTAF(\@tempTAFElements, \@TAFElements, "$time", "$numElements");
		}
	}
#Reset the terminal color
print RESET;

sub processMETAR($)
	{
		#station is passed in
		my $station = $_[0];	
		
		#convert the station to upper case, then get the metar and remove the time and spaces
		$station = uc($station);	
		my $METARoutput =`curl -s http://weather.noaa.gov/pub/data/observations/metar/stations/$station.TXT`;
		$METARoutput =~ s/\d{4}\/\d{2}\/\d{2}\s+\d{2}:\d{2}//;
		$METARoutput =~ s/\n//g;
		
		return $METARoutput;
	}

sub parseVisibilityMETAR($)
	{
		#metar is passed in
		my $METAR = $_[0];

		#parse out the visibility data to handle all cases: e.g. 1 1/2SM, 1SM 3/4SM.  Need to make
		#this a switch eventually but I couldn't get it to work
		if($METAR =~ / *(\d+)[- ]+(\d+) *\/ *(\d)+(\D.*)?SM/) {
			return $1+$2/$3;
		} elsif($METAR =~ / *(\d+) *\/ *(\d+)(\D.*)?SM/) {
			return $1/$2;
		} elsif($METAR =~ / *(\d+)SM/) {
			return $1;
		}
	}
	
sub checkVisibility($)
	{
		#visibility is passed in.  Set flightRule var to null to use later
		use Switch;
		my $visibility = $_[0];
		my $flightRule = ""; 
		
		#set flight rule based on visibility
		switch ($visibility)
			{
				case { $visibility == "" } {$flightRule = "NULL"; }
				case { $visibility < 1 } {$flightRule = "LIFR"; }
				case { ($visibility >= 1) && ($visibility < 3) } {$flightRule = "IFR"; }
				case { ($visibility >= 3) && ($visibility <= 5) } {$flightRule = "MVFR"; }
				case { $visibility > 5 } {$flightRule = "VFR"; }
			}
		return $flightRule;
	}
	
sub checkClouds($)
	{
		#cloud data is passed in as an array to handle a case like Broken 800 Overcast 3400, etc
		#initialize flightRule variable to null
		use Switch;
		my (@clouds) = @_;
		my $flightRule = "";
		
		#iterate through the cloud layers and check for non-VFR conditions.  Once a non-VFR
		#condition is found, break out of the loop so that a higher cloud layer doesn't incorrectly
		#set the flight rule to VFR
		CLOUDS: foreach my $cloudLayer (@clouds)
			{
				if ($cloudLayer =~ /Overcast at (\d+)ft/ || $cloudLayer =~ /Broken at (\d+)ft/ || $cloudLayer =~ /Overcast Cumulonimbus at (\d+)ft/)
				{
					my $ceiling = $1;
						
					switch ($ceiling)
					{
						case { $ceiling == ""} { $flightRule = "NULL"; }
						case { $ceiling < 500 } { $flightRule = "LIFR"; last CLOUDS; }
						case { $ceiling < 1000 } { $flightRule = "IFR"; last CLOUDS; }
						case { ($ceiling >= 1000) && ($ceiling <= 3000) } { $flightRule = "MVFR"; last CLOUDS; }
						case { $ceiling > 3000 } { $flightRule = "VFR"; }
					}
				}
				else
				{
					$flightRule = "VFR";
				}
			}
		return $flightRule;
	}

sub fetchTAF($)
	{
		#station is passed in and converted to upper case
		$station = $_[0];	
		$station = uc($station);
		
		#get TAF and remove data and newlines
		my $TAFoutput = `curl -s http://weather.noaa.gov/pub/data/forecasts/taf/stations/$station.TXT`;
		$TAFoutput =~ s/\d{4}\/\d{2}\/\d{2}\s+\d{2}:\d{2}//;
		$TAFoutput =~ s/\n//;
		return $TAFoutput;
	}
sub processTAF($)
	{
		#TAF is passed in.  A temp copy is made for processing
		my $TAFoutput = $_[0];
		my $tempTAF = $TAFoutput;
		
		#The TAF needs to look like a METAR so I remove the trend info from the temp copy
		$tempTAF =~ s/TEMPO//g;
		$tempTAF =~ s/BECMG//g;
		return $tempTAF;
	}
sub extractTime($)
	{	
		#the temp taf is passed in
		my $tempTAF = $_[0];
		
		#remove the time and store it so it can be used in along with the station to mimic
		#a METAR
		$tempTAF =~ /\s(\d+)Z\s/;
		my $time = $1 . "Z";
		return $time;
	}
sub parseVisibilityTAF($)
	{
		#TAF is passed in
		my $TAF = $_[0];
		
		#parse out the visibility data to handle all cases: e.g. 1 1/2SM, 1SM 3/4SM.  Need to make
		#this a switch eventually but I couldn't get it to work
		if($TAF =~ /(\d+)\s(\d)\/(\d)SM/) { return $1+$2/$3; }
		elsif ($TAF =~ /(\d)\/(\d)SM/) { return $1/$2; }
		elsif ($TAF =~ /(\d+)SM/) { return $1; }
	}

sub outputMETAR($)
	{
		use Switch;
		
		#Visibility and ceiling flight rules are passed in and the overall flight rule is set
		#output the METAR and set color based on flight rule
		my $visibilityFlightRule = $_[0];
		my $ceilingFlightRule = $_[1];
		my $METARoutput = $_[2];
		my $METARAge = $_[3];
		
		#call function which sets overall flight rule
		my $flightRule = &setFlightRule($visibilityFlightRule, $ceilingFlightRule);
		
		#output METAR and age.  Use bold if calling from geektool (-b)
		if (!$options{b})
		{
		switch ($flightRule)
			{
				case "VFR" { if($METARAge <= 70) { print GREEN, "$METARoutput" . " \($METARAge mins ago\)" . "\n"; } else { print YELLOW, "$METARoutput" . " \($METARAge mins ago\)" . "\n";} }
				case "LIFR" { if($METARAge <= 70) { print MAGENTA, "$METARoutput" . " \($METARAge mins ago\)" . "\n"; } else { print YELLOW, "$METARoutput" . " \($METARAge mins ago\)" . "\n";} }
				case "IFR" { if($METARAge <= 70) { print RED, "$METARoutput" . " \($METARAge mins ago\)" . "\n"; } else { print YELLOW, "$METARoutput" . " \($METARAge mins ago\)" . "\n";} }
				case "MVFR" { if($METARAge <= 70) { print BLUE, "$METARoutput" . " \($METARAge mins ago\)" . "\n"; } else { print YELLOW, "$METARoutput" . " \($METARAge mins ago\)" . "\n";} }
				case "NULL" { if($METARAge <= 70) { print WHITE, "$METARoutput" . " \($METARAge mins ago\)" . "\n"; } else { print YELLOW, "$METARoutput" . " \($METARAge mins ago\)" . "\n";} }
			}
		}
		else 
		{
		switch ($flightRule)
			{
				case "VFR" { if($METARAge <= 70) { print BOLD GREEN, "$METARoutput" . " \($METARAge mins ago\)" . "\n"; } else { print BOLD YELLOW, "$METARoutput" . " \($METARAge mins ago\)" . "\n";} }
				case "LIFR" { if($METARAge <= 70) { print BOLD MAGENTA, "$METARoutput" . " \($METARAge mins ago\)" . "\n"; } else { print BOLD YELLOW, "$METARoutput" . " \($METARAge mins ago\)" . "\n";} }
				case "IFR" { if($METARAge <= 70) { print BOLD RED, "$METARoutput" . " \($METARAge mins ago\)" . "\n"; } else { print BOLD YELLOW, "$METARoutput" . " \($METARAge mins ago\)" . "\n";} }
				case "MVFR" { if($METARAge <= 70) { print BOLD BLUE, "$METARoutput" . " \($METARAge mins ago\)" . "\n"; } else { print BOLD YELLOW, "$METARoutput" . " \($METARAge mins ago\)" . "\n";} }
				case "NULL" { if($METARAge <= 70) { print BOLD WHITE, "$METARoutput" . " \($METARAge mins ago\)" . "\n"; } else { print BOLD YELLOW, "$METARoutput" . " \($METARAge mins ago\)" . "\n";} }
			}
		}		
	}
	
sub setFlightRule($)
	{
		#both flight rules are passed in
		my $visibilityFlightRule = $_[0];
		my $ceilingFlightRule = $_[1];
		
		#set flight rule based on the visibility and ceiling flight rules.  Select the lower
		#of the two
		if ($visibilityFlightRule eq "LIFR" || $ceilingFlightRule eq "LIFR") { $flightRule = "LIFR"; }
		elsif ($visibilityFlightRule eq "IFR" || $ceilingFlightRule eq"IFR") { $flightRule = "IFR"; }
		elsif ($visibilityFlightRule eq "MVFR" || $ceilingFlightRule eq "MVFR") { $flightRule = "MVFR"; }
		elsif ($visibilityFlightRule eq "NULL" || $ceilingFlightRule eq "NULL") { $flightRule = "NULL"; }
		else { $flightRule = "VFR"; }
		
		return $flightRule;
	}
	
sub outputTAF($)
	{
		use Switch;
		
		#pass in the TAF and temp TAF as arrays
		#pass in the time and number of elements
		my ( $tempTAFElementsRef, $TAFElementsRef ) = @_;
		my $time = $_[2];
		my $numElements = $_[3];
		
		#dereference the arrays and set to local arrays
		my @tempTAFElements = @{$tempTAFElementsRef};
		my @TAFElements = @{$TAFElementsRef};
		
		#iterate through the elements.  Skip the first one because it just says "TAF"
		for ( my $i = 0; $i < $numElements; $i++ )
			{
				#pull out the visibility from the temp copy and set flight rule
				my $visibility = &parseVisibilityTAF("$tempTAFElements[$i]");
				my $visibilityFlightRule = &checkVisibility("$visibility");
				
				#make the TAF look like a METAR by adding the station and time to it
				my $taf = $station . " " . $time . " " . $tempTAFElements[$i];
				my $t=new Geo::METAR;
				$t->metar("$taf");
				
				#set the flight rule based on the cloud layers.  The sky variable is an array
				#returned from the lib to account for various cloud layers
				my @clouds = @{$t->SKY};
				my $ceilingFlightRule = &checkClouds(@clouds);
				
				#set flight overall flight rule
				my $flightRule = &setFlightRule($visibilityFlightRule, $ceilingFlightRule);
		
				#output TAF and account for first line.  Use bold if calling from geektool (-b)
				if(!$options{b})
				{
				switch ($flightRule)
					{
						case "VFR" {
							print GREEN, "$TAFElements[$i]\n";
							}
						case "LIFR" {
							print MAGENTA, "$TAFElements[$i]\n";
							}
						case "IFR" {
							print RED, "$TAFElements[$i]\n";
							}
						case "MVFR" {
							print BLUE, "$TAFElements[$i]\n"; 
							}
					}
				}
				else
				{
				switch ($flightRule)
					{
						case "VFR" {
							print BOLD GREEN, "$TAFElements[$i]\n"; 
							}
						case "LIFR" {
							print BOLD MAGENTA, "$TAFElements[$i]\n";
							}
						case "IFR" {
							print BOLD RED, "$TAFElements[$i]\n";
							}
						case "MVFR" {
							print BOLD BLUE, "$TAFElements[$i]\n";
							}
					}
				}
			}
	}
sub compareTime($)
{
	#package to calculate GMT in seconds
	use Time::Local;
	
	my $metarDate = $_[0];
	my $metarTime = $_[1];
	
	#extract hours and minutes from the time
	my @metarHoursMinutes = split(':', $metarTime);
	my $metarHours = $metarHoursMinutes[0];
	my $metarMinutes = $metarHoursMinutes[1];
	$metarMinutes =~ /(\d+)\sUTC/;
	$metarMinutes = $1;
	
	#get the current Zulu (UTC) time
	my @currentTime = gmtime(time);
	my $minutes = $currentTime[1];
	my $hours = $currentTime[2];
	my $day = $currentTime[3];
	
	#convert times to seconds from epoch
	my $gmSecsMETAR = timegm(0,$metarMinutes,$metarHours,$metarDate,0,0);
	my $gmSecsLocal = timegm(0,$minutes,$hours,$day,0,0);
	
	#find the difference in minutes
	my $METARAgeMins = abs(($gmSecsLocal - $gmSecsMETAR) / 60);
	
    #my ($sec, $min, $hour, $day,$month,$year) = (localtime($gmSecsMETAR))[0,1,2,3,4,5];
    #print $hour . ":" . $min;
	
	return $METARAgeMins;
}	

	